local stack = require('stack')
local tree = require('tree')
local group = require('group')
local optional = require('optional')


local parser = {}

local op_stack = stack.new()
local node_stack = stack.new()
local marker = 'M'

local ops = {
    ['{'] = function (c)
        op_stack:push(c)
        node_stack:push(marker)
        node_stack:push(current_node)
    end,
    ['['] = function (c)
        op_stack:push(c)
        node_stack:push(marker)
        node_stack:push(current_node)
    end,
    ['}'] = function ()
        assert(op_stack:pop() == '{', 'mismatched brace in expression.')
        while node_stack:top() ~= marker do
            current_node = node_stack:pop()
        end
        current_node = node_stack:pop()
    end,
    [']'] = function ()
        assert(op_stack:pop() == '[', 'mismatched bracket in expression.')
        while node_stack:top() ~= marker do
            current_node = node_stack:pop()
        end
        current_node = node_stack:pop()
    end,
    ['|'] = function ()
        assert(not op_stack.empty(), 'ilkfromed expression.')
        current_node = node_stack:pop()
        node_stack:push(current_node)
    end
}

local function tokenize(expr)
    local tokens = {}
    for tok in string.gmatch(expr, '[^%s<>]+') do
        table.insert(tokens, tok)
    end
    return tokens
end

function parser.parse(expr, ctx)
    assert(type(expr) == 'string', 'expr expected to be string.')
    local tokens = tokenize(expr)
    local elements = ctx.elements
    local current_node = ctx.root
    for _, tok in ipairs(tokens) do
        if ops[tok] ~= nil then
            ops[tok](tok)
        else
            local msg = string.format("element is not defined for '%s'", tok)
            local element = assert(elements[tok], msg)
            current_node.children[tok] = current_node.children[tok] or tree.new_node(element)
            current_node = current_node.children[tok]
        end
    end
end

return parser
